/*
 * spi_flash_W25Q64.c
 *
 *  Created on: Oct 19, 2022
 *      Author: yu
 */

#include "spi_flash_W25Q64.h"
#include "gpio.h"
#include "spi.h"

#define INSTX_WRITE_ENABLE          0x06
#define INSTX_JEDEC_ID              0x9F
#define INSTX_READ_DATA             0x03
#define INSTX_PAGE_PROGRAM          0x02
#define INSTX_SECTOR_ERASE          0x20
#define INSTX_BLOCK_ERASE_32KB      0x52
#define INSTX_BLOCK_ERASE_64KB      0xD8
#define INSTX_CHIP_ERASE            0xC7
#define INSTX_READ_STATUS1          0x05

#define BUSY_BIT_MASK               0x01

#define SPI_FLASH_CS_LO()           LL_GPIO_ResetOutputPin(SPI_FLASH_CS_GPIO_Port, SPI_FLASH_CS_Pin)
#define SPI_FLASH_CS_HI()           LL_GPIO_SetOutputPin(SPI_FLASH_CS_GPIO_Port, SPI_FLASH_CS_Pin)

#define SPI_FLASH_PAGE_BYTES        256
#define SPI_FLASH_SECTOR_BYTES      (SPI_FLASH_PAGE_BYTES*16) //4KB
#define SPI_FLASH_BLOCK_BYTES       (SPI_FLASH_SECTOR_BYTES*16) //64KB

uint8_t u8Read_S1 = INSTX_READ_STATUS1;
uint8_t u8Status1;

//-private code------------------------------------------------------------------
static uint8_t s_u8ReadStatus1(void);
static void s_WaitForWriteEnd(void);
static void s_WriteEnable(void);
static void s_PageProgram(uint8_t *pWrData, uint32_t u32Addr, uint16_t u16BytesNum);

/********************************************************************************
*函数功能: SPI发送函数
*修改记录:
*日期:                             作者:                             摘要:
      2022年10月25日      YummoShin         Create
********************************************************************************/
static void s_SPI_Transmit(SPI_TypeDef *SPIx, uint8_t *pTxData, uint16_t Size)
{
  uint32_t u32Count=SystemCoreClock/50;

  while(Size)
  {

    if(LL_SPI_IsActiveFlag_TXE(SPIx)) //等待发送空
    {
      LL_SPI_TransmitData8(SPIx, *pTxData);
      pTxData++;
      Size--;

      u32Count=SystemCoreClock/50;
    }
    else if(0==u32Count) //超时
    {
      return;
    }

    u32Count--;
  }

  u32Count=SystemCoreClock/50;
  while(LL_SPI_IsActiveFlag_BSY(SPIx)) //等待BUSY位
  {
    if(0==u32Count) //超时
    {
      return;
    }
    u32Count--;
  }

  LL_I2S_ClearFlag_OVR(SPIx);
}

/********************************************************************************
*函数功能:
*修改记录:
*日期:                             作者:                             摘要:
      Oct 29, 2022      YummoShin         Create
********************************************************************************/
static void s_SPI_TransmitOneByte(uint8_t u8Data)
{
  while(LL_SPI_IsActiveFlag_TXE(SPI1)==0); //等待发送空
  LL_SPI_TransmitData8(SPI1, u8Data);
}

/********************************************************************************
*函数功能: SPI接收函数
*修改记录:
*日期:                             作者:                             摘要:
      2022年10月25日      YummoShin         Create
********************************************************************************/
static void s_SPI_Receive(SPI_TypeDef *SPIx, uint8_t *pRxData, uint16_t Size)
{
  uint32_t u32Count=SystemCoreClock/50;
  uint8_t u8TxAllowed=1;

  LL_I2S_ClearFlag_OVR(SPIx);
  while(Size)
  {
    if(LL_SPI_IsActiveFlag_TXE(SPIx) && u8TxAllowed==1) //等待发送空
    {
      LL_SPI_TransmitData8(SPIx, 0xA5);

      u8TxAllowed = 0;
    }

    if(LL_SPI_IsActiveFlag_RXNE(SPIx))
    {
      *pRxData = LL_SPI_ReceiveData8(SPIx);
      pRxData++;
      Size--;

      u8TxAllowed = 1;
      u32Count=SystemCoreClock/50;
    }
    else if(0==u32Count)
    {
      return;
    }

    u32Count--;
  }

  u32Count=SystemCoreClock/50;
  while(LL_SPI_IsActiveFlag_BSY(SPIx)) //等待BUSY位
  {
    if(0==u32Count) //超时
    {
      return;
    }
    u32Count--;
  }

  LL_I2S_ClearFlag_OVR(SPIx);
}

/********************************************************************************
*函数功能: 读取STATUS 1，
* busy status is set to 1 when executing a page program, sector erase, block erase,
* chip erase, write status register or erase/program security register instruction
*修改记录:
*日期:                             作者:                             摘要:
      2022年10月21日      YummoShin         Create
********************************************************************************/
static uint8_t s_u8ReadStatus1(void)
{
  uint8_t u8TxData=INSTX_READ_STATUS1;

  SPI_FLASH_CS_LO();
  s_SPI_Transmit(SPI1, &u8TxData, 1);
  s_SPI_Receive(SPI1, &u8Status1, 1);
  SPI_FLASH_CS_HI();

  return u8Status1;
}

/********************************************************************************
*函数功能: 等待写BUSY位清零
* busy status is set to 1 when executing a page program, sector erase, block erase,
* chip erase, write status register or erase/program security register instruction
*修改记录:
*日期:                             作者:                             摘要:
      2022年10月21日      YummoShin         Create
********************************************************************************/
static void s_WaitForWriteEnd(void)
{
  SPI_FLASH_CS_LO();
  s_SPI_Transmit(SPI1, &u8Read_S1, 1);
  do
  {
    s_SPI_Receive(SPI1, &u8Status1, 1);
  }while((u8Status1&BUSY_BIT_MASK)==BUSY_BIT_MASK);
  SPI_FLASH_CS_HI();
}

/********************************************************************************
*函数功能: write enable
* A write disable state occurs upon power-up or after any of the following instructions:
* Write Disable, Page Program, Quad Page Program, Sector Erase, Block Erase,
* Chip Erase, Write Status Register, Erase Security Register and
* Program Security Register.
*修改记录:
*日期:                             作者:                             摘要:
      Oct 21, 2022      YummoShin         Create
********************************************************************************/
static void s_WriteEnable(void)
{
  uint8_t u8TxData=INSTX_WRITE_ENABLE;

  SPI_FLASH_CS_LO();
  s_SPI_Transmit(SPI1, &u8TxData, 1);
  SPI_FLASH_CS_HI();
}

/********************************************************************************
*函数功能: 读取JEDEC ID
*修改记录:
*日期:                             作者:                             摘要:
      Oct 21, 2022      YummoShin         Create
********************************************************************************/
uint32_t u32SPI_Flash_ReadJedecId(void)
{
  uint32_t u32JedecId;
  uint8_t u8RxData[3], TxData=INSTX_JEDEC_ID;

  SPI_FLASH_CS_LO();
  s_SPI_Transmit(SPI1, &TxData, 1);
  s_SPI_Receive(SPI1, u8RxData, 3);
  SPI_FLASH_CS_HI();

  u32JedecId = u8RxData[0]<<16 | u8RxData[1]<<8 | u8RxData[2];

  return u32JedecId;
}

/********************************************************************************
*函数功能: 读取数据
*修改记录:
*日期:                             作者:                             摘要:
      2022年10月23日      YummoShin         Create
********************************************************************************/
void SPI_Flash_ReadData(uint8_t *pRdData, uint32_t u32Addr, uint32_t u32BytesNum)
{
  uint8_t u8TxData[4];

  u8TxData[0] = INSTX_READ_DATA;
  u8TxData[1] = (u32Addr&0xFF0000)>>16;
  u8TxData[2] = (u32Addr&0xFF00)>>8;
  u8TxData[3] = u32Addr&0xFF;

  SPI_FLASH_CS_LO();
  s_SPI_Transmit(SPI1, u8TxData, 4);
  s_SPI_Receive(SPI1, pRdData, u32BytesNum);
  SPI_FLASH_CS_HI();
}

/********************************************************************************
*函数功能: 页写入命令，写入字节需要小于等于256
*修改记录:
*日期:                             作者:                             摘要:
      2022年10月23日      YummoShin         Create
********************************************************************************/
static void s_PageProgram(uint8_t *pWrData, uint32_t u32Addr, uint16_t u16BytesNum)
{
  s_WriteEnable();

  SPI_FLASH_CS_LO();
  s_SPI_TransmitOneByte(INSTX_PAGE_PROGRAM);
  s_SPI_TransmitOneByte((u32Addr&0xFF0000)>>16);
  s_SPI_TransmitOneByte((u32Addr&0xFF00)>>8);
  s_SPI_TransmitOneByte(u32Addr&0xFF);
  s_SPI_Transmit(SPI1, pWrData, u16BytesNum);
  SPI_FLASH_CS_HI();

  s_WaitForWriteEnd();
}

/********************************************************************************
*函数功能: 写入数据，与FLASH PAGE BYTES 对齐，写入前要先擦除
*修改记录:
*日期:                             作者:                             摘要:
      2022年10月23日      YummoShin         Create
********************************************************************************/
void SPI_FLASH_WriteData(uint8_t *pWrData, uint32_t u32Addr, uint32_t u32BytesNum)
{
  uint32_t u32BytesToWrite=0;

  //先处理一页以及一页内的数据
  u32BytesToWrite = SPI_FLASH_PAGE_BYTES-(u32Addr%SPI_FLASH_PAGE_BYTES);
  if(u32BytesToWrite>u32BytesNum)
  {
    u32BytesToWrite = u32BytesNum;
  }
  s_PageProgram(pWrData, u32Addr, u32BytesToWrite);

  u32Addr += u32BytesToWrite;
  pWrData += u32BytesToWrite;
  u32BytesNum -= u32BytesToWrite;

  //有剩余页数处理
  if(u32BytesNum > 0)
  {
    uint32_t u32Pages=u32BytesNum/SPI_FLASH_PAGE_BYTES;
    uint32_t u32BytesLeft=u32BytesNum%SPI_FLASH_PAGE_BYTES;

    for(; u32Pages>0; u32Pages--)
    {
      u32BytesToWrite = SPI_FLASH_PAGE_BYTES;
      s_PageProgram(pWrData, u32Addr, u32BytesToWrite);
      u32Addr += u32BytesToWrite;
      pWrData += u32BytesToWrite;
      u32BytesNum -= u32BytesToWrite;
    }

    if(u32BytesLeft>0)
    {
      u32BytesToWrite = u32BytesLeft;
      s_PageProgram(pWrData, u32Addr, u32BytesToWrite);
      u32Addr += u32BytesToWrite;
      pWrData += u32BytesToWrite;
      u32BytesNum -= u32BytesToWrite;
    }
  }
}

/**
  * @brief  Writes block of data to the FLASH. In this function, the number of
  *         WRITE cycles are reduced, using Page WRITE sequence.
  * @param  pBuffer: pointer to the buffer  containing the data to be written
  *         to the FLASH.
  * @param  WriteAddr: FLASH's internal address to write to.
  * @param  NumByteToWrite: number of bytes to write to the FLASH.
  * @retval None
  */
#define sFLASH_SPI_PAGESIZE   0x100

void SPI_FLASH_WriteData0(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
{
  uint32_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;

  Addr = WriteAddr % sFLASH_SPI_PAGESIZE;
  count = sFLASH_SPI_PAGESIZE - Addr;
  NumOfPage =  NumByteToWrite / sFLASH_SPI_PAGESIZE;
  NumOfSingle = NumByteToWrite % sFLASH_SPI_PAGESIZE;

  if (Addr == 0) /*!< WriteAddr is sFLASH_PAGESIZE aligned  */
  {
    if (NumOfPage == 0) /*!< NumByteToWrite < sFLASH_PAGESIZE */
    {
      //sFLASH_WritePage(pBuffer, WriteAddr, NumByteToWrite);
      s_PageProgram(pBuffer, WriteAddr, NumByteToWrite);
    }
    else /*!< NumByteToWrite > sFLASH_PAGESIZE */
    {
      while (NumOfPage--)
      {
        while(LL_USART_IsActiveFlag_TXE(USART1)==RESET);
        LL_USART_TransmitData8(USART1, NumOfPage);

        uint8_t u8Address[4];

        u8Address[0] = (WriteAddr>>24)&0xFF;
        u8Address[1] = (WriteAddr>>16)&0xFF;
        u8Address[2] = (WriteAddr>>8)&0xFF;
        u8Address[3] = (WriteAddr)&0xFF;
        for(uint8_t i=0; i<4; i++)
        {
          while(LL_USART_IsActiveFlag_TXE(USART1)==RESET);
          LL_USART_TransmitData8(USART1, u8Address[i]);
        }

        //sFLASH_WritePage(pBuffer, WriteAddr, sFLASH_SPI_PAGESIZE);
        s_PageProgram(pBuffer, WriteAddr, sFLASH_SPI_PAGESIZE);
        WriteAddr =  WriteAddr + sFLASH_SPI_PAGESIZE;
        pBuffer = pBuffer + sFLASH_SPI_PAGESIZE;
      }

      //sFLASH_WritePage(pBuffer, WriteAddr, NumOfSingle);
      s_PageProgram(pBuffer, WriteAddr, NumOfSingle);
    }
  }
  else /*!< WriteAddr is not sFLASH_PAGESIZE aligned  */
  {
    if (NumOfPage == 0) /*!< NumByteToWrite < sFLASH_PAGESIZE */
    {
      if (NumOfSingle > count) /*!< (NumByteToWrite + WriteAddr) > sFLASH_PAGESIZE */
      {
        temp = NumOfSingle - count;

        //sFLASH_WritePage(pBuffer, WriteAddr, count);
        s_PageProgram(pBuffer, WriteAddr, count);
        WriteAddr +=  count;
        pBuffer += count;

        //sFLASH_WritePage(pBuffer, WriteAddr, temp);
        s_PageProgram(pBuffer, WriteAddr, temp);
      }
      else
      {
        //sFLASH_WritePage(pBuffer, WriteAddr, NumByteToWrite);
        s_PageProgram(pBuffer, WriteAddr, NumByteToWrite);
      }
    }
    else /*!< NumByteToWrite > sFLASH_PAGESIZE */
    {
      NumByteToWrite -= count;
      NumOfPage =  NumByteToWrite / sFLASH_SPI_PAGESIZE;
      NumOfSingle = NumByteToWrite % sFLASH_SPI_PAGESIZE;

      //sFLASH_WritePage(pBuffer, WriteAddr, count);
      s_PageProgram(pBuffer, WriteAddr, count);
      WriteAddr +=  count;
      pBuffer += count;

      while (NumOfPage--)
      {
        //sFLASH_WritePage(pBuffer, WriteAddr, sFLASH_SPI_PAGESIZE);
        s_PageProgram(pBuffer, WriteAddr, sFLASH_SPI_PAGESIZE);
        WriteAddr +=  sFLASH_SPI_PAGESIZE;
        pBuffer += sFLASH_SPI_PAGESIZE;
      }

      if (NumOfSingle != 0)
      {
        //sFLASH_WritePage(pBuffer, WriteAddr, NumOfSingle);
        s_PageProgram(pBuffer, WriteAddr, NumOfSingle);
      }
    }
  }
}

/********************************************************************************
*函数功能: 擦除扇区，对齐到4K
*修改记录:
*日期:                             作者:                             摘要:
      2022年10月23日      YummoShin         Create
********************************************************************************/
void SPI_FLASH_SectorErase(uint32_t u32SectorAddr)
{
  uint8_t u8TxData[4];

  s_WriteEnable();

  u8TxData[0] = INSTX_SECTOR_ERASE;
  u8TxData[1] = (u32SectorAddr&0xFF0000)>>16;
  u8TxData[2] = (u32SectorAddr&0xFF00)>>8;
  u8TxData[3] = u32SectorAddr&0xFF;

  SPI_FLASH_CS_LO();
  s_SPI_Transmit(SPI1, u8TxData, 4);
  SPI_FLASH_CS_HI();

  s_WaitForWriteEnd();
}

/********************************************************************************
*函数功能: 擦除块，对齐到64K
*修改记录:
*日期:                             作者:                             摘要:
      2022年10月23日      YummoShin         Create
********************************************************************************/
void SPI_FLASH_BlockErase(uint32_t u32BlockAddr)
{
  uint8_t u8TxData[4];

  s_WriteEnable();

  u8TxData[0] = INSTX_BLOCK_ERASE_64KB;
  u8TxData[1] = (u32BlockAddr&0xFF0000)>>16;
  u8TxData[2] = (u32BlockAddr&0xFF00)>>8;
  u8TxData[3] = u32BlockAddr&0xFF;

  SPI_FLASH_CS_LO();
  s_SPI_Transmit(SPI1, u8TxData, 4);
  SPI_FLASH_CS_HI();

  s_WaitForWriteEnd();
}

/********************************************************************************
*函数功能: 全片擦除
*修改记录:
*日期:                             作者:                             摘要:
      2022年10月23日      YummoShin         Create
********************************************************************************/
void SPI_FLASH_ChipErase(void)
{
  uint8_t u8TxData;

  s_WriteEnable();

  u8TxData = INSTX_CHIP_ERASE;

  SPI_FLASH_CS_LO();
  s_SPI_Transmit(SPI1, &u8TxData, 1);
  SPI_FLASH_CS_HI();

  s_WaitForWriteEnd();
}
